﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;

namespace backupmanagement
{
    public class FileDown
    {
        #region 变量
        public long FileSize = 0;
        private int ThreadNum = 1;
        public int DownLoadLength = 0;
        private string downUrl = string.Empty;
        private string saveDir = string.Empty;

        private int _downLength = 0;
        private string _tempSavePath = string.Empty;
        #endregion

        #region 构造函数
        public FileDown(string downUrl, string saveDir, int thNum = 10)
        {
            this.downUrl = downUrl;
            this.saveDir = saveDir;
            try
            {
                var request = (HttpWebRequest)HttpWebRequest.Create(downUrl);
                FileSize = request.GetResponse().ContentLength;
                if (FileSize / 2048 / 2048 >= 30)
                    ThreadNum = thNum;
                else
                    ThreadNum = 1;
                request.Abort();
            }
            catch (Exception er)
            {
                throw er;
            }
        }
        #endregion

        private string SavePath
        {
            get
            {
                Uri uri = new Uri(downUrl);
                string _dirpath = downUrl.Substring(downUrl.IndexOf(uri.Host), downUrl.Length - downUrl.IndexOf(uri.Host));
                return saveDir + _dirpath.Replace(uri.Host, "").Replace("/", "\\");
            }
        }

        public void Start()
        {
            string _path = SavePath.Substring(0, SavePath.LastIndexOf("\\"));
            _tempSavePath = (_path + "\\Temp").Replace("\\\\", "\\");
            if (!Directory.Exists(_tempSavePath))
            {
                Directory.CreateDirectory(_tempSavePath);
                File.SetAttributes(_tempSavePath, FileAttributes.Hidden);
            }

            //多线程下载
            _downLength = (int)FileSize / ThreadNum;
            List<DownModule> ldm = new List<DownModule>();
            DownModule dm = new DownModule();
            for (int i = 0; i < ThreadNum; i++)
            {
                dm = new DownModule();
                dm.filePath = _tempSavePath + "\\" + DateTime.Now.Ticks + i.ToString() + ".dat";
                if (i < ThreadNum - 1)
                {
                    dm.start = _downLength * i;
                    dm.end = dm.start + _downLength - 1;
                }
                else
                {
                    dm.start = _downLength * i;
                    dm.end = (int)FileSize - 1;
                }
                dm.downUrl = this.downUrl;
                ldm.Add(dm);
                dm.Start();
            }
            new Thread(() => { MergeFiles(ldm); }).Start();
        }

        void MergeFiles(List<DownModule> ldm)
        {
            bool isComplete = false;
            int downLen = 0;
            while (true)//等候
            {
                isComplete = true;
                downLen = 0;
                foreach (var dm in ldm)
                {
                    downLen += dm.downlength;
                    if (dm.isComplete == false)//有未结束线程，等候
                    {
                        isComplete = false;
                        if (dm.reTest <= 10000 && !string.IsNullOrWhiteSpace(dm.errMsg))
                        {
                            dm.errMsg = null;
                            dm.Start();
                        }
                    }
                }
                DownLoadLength = downLen;
                if (isComplete == true)//所有线程均已结束，结束等候，
                {
                    break;
                }
                else
                {
                    Thread.Sleep(100);
                }
            }
            FileStream fs;//开端合并
            FileStream fstemp;
            int readfile;
            byte[] bytes = new byte[2048];
            fs = new FileStream(SavePath, System.IO.FileMode.Create);
            for (int k = 0; k < ThreadNum; k++)
            {
                fstemp = new FileStream(ldm[k].filePath, System.IO.FileMode.Open);
                while (true)
                {
                    readfile = fstemp.Read(bytes, 0, 2048);
                    if (readfile > 0)
                    {
                        fs.Write(bytes, 0, readfile);
                    }
                    else
                    {
                        break;
                    }
                }
                fstemp.Close();
            }
            fs.Close();
            ldm.ForEach(dm =>
            {
                File.Delete(dm.filePath);
            });
        }
    }
    class DownModule
    {
        public string filePath { get; set; }
        public string downUrl { get; set; }
        public int start { get; set; }
        public int end { get; set; }
        public bool isComplete { get; set; }
        public string errMsg { get; set; }
        public int reTest = 0;
        public int downlength = 0;
        private Thread thread { get; set; }
        void Down()
        {
            FileStream fs = new FileStream(filePath, System.IO.FileMode.OpenOrCreate);
             Stream ns = null;
             try
             {
                 isComplete = false;
                 var request = (HttpWebRequest)HttpWebRequest.Create(downUrl);
                 //接收的起始地位及接收的长度
                 request.AddRange(start, end);
                 ns = request.GetResponse().GetResponseStream();//获得接收流
                 Byte[] nbytes = new byte[2048];
                 int nreadsize = ns.Read(nbytes, 0, 2048);
                 while (nreadsize > 0)
                 {
                     fs.Write(nbytes, 0, nreadsize);
                     nreadsize = ns.Read(nbytes, 0, 2048);
                     downlength += 2048;
                 }
                 fs.Close();
                 ns.Close();
                 isComplete = true;
                 thread = null;
             }
             catch (Exception er)
             {
                 errMsg = er.Message;
                 reTest++;
                 fs.Close();
                 if (ns != null)
                 {
                     ns.Close();
                 }
                 thread = null;
             }
        }
        public void Start()
        {
            thread = new Thread(new ThreadStart(Down));
            thread.IsBackground = true;
            thread.Start();
        }
    }
}
